package cinvestment;

import ilog.concert.IloIntVar;
import ilog.concert.IloLinearNumExpr;
import ilog.concert.IloNumVar;
import ilog.concert.IloRange;
import ilog.cplex.IloCplex;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import support.AlgorithmSupport;
import util.Lang;
import util.NullOutputStream;

/**
 * using multi-cut L shaped method, considering fixed cost or no fixed cost
 * **/
public class CapacityInvestmentMultiCut {

	public CapacityInvestmentMultiCut(double delta, int productTypes,
			int simulationCount, double[] penalties, double c1, double ratio) {
		this.delta = delta;
		this.N = productTypes;
		this.simulatedCount = simulationCount;
		this.p = Lang.clone(penalties);
		this.c1 = c1;
		this.ratio = ratio;
	}

	private double variance_demand = 0.3;

	public void setVarianceDemand(double value) {
		this.variance_demand = value;
	}

	private List<Plant> plants;
	private double[][] demands;

	/**
	 * Number of kinds of products
	 */
	private int N;

	/**
	 * Penalty cost for unfilled demand
	 * **/
	// private double p = 1.0;
	// try to vary the penalty cost of different products
	private double[] p;

	/**
	 * Number of plants
	 */
	private int n;

	/**
	 * Tolerance for the gap between Upper bound and lower bound
	 * **/
	private double epsilon = 0.0001;

	/**
	 * Basic cost for flexible resource cost
	 * **/
	private double c1;
	private double delta;

	private double ratio;
	private double fixedCost;

	private BigDecimal probibility;

	/**
	 * Sampling count of demand
	 * **/
	private int simulatedCount;

	/**
	 * iteration counter
	 * **/
	private int iter = 1;

	private double[] flexibleCosts;

	private String structure = AlgorithmSupport.EXTENSIVE;

	public void setStructure(String structure) {
		this.structure = structure;
	}

	private void init() {
		// this.demands = AlgorithmSupport.getDemands_uniform(simulatedCount, N,
		// 0.0, 2.0);
		this.demands = AlgorithmSupport.getDemands_normal(simulatedCount, N,
				1.0, this.variance_demand, 0, 2.0);

		this.plants = AlgorithmSupport.createPlantsByStructure(N, structure);

		this.n = this.plants.size();

		this.probibility = BigDecimal.ONE.divide(
				BigDecimal.valueOf(demands.length), 6,
				BigDecimal.ROUND_HALF_EVEN);
		this.fixedCost = BigDecimal.valueOf(c1)
				.multiply(BigDecimal.valueOf(ratio)).doubleValue();

		flexibleCosts = new double[n];
		for (int j = 0; j < n; j++)
			flexibleCosts[j] = this.getFlexibleCost(plants.get(j)
					.getAvailableProductsNumber());

	}

	public List<Double[]> lshaped() {
		this.init();

		List<Double[]> finalResult = new ArrayList<>();

		double[] Kv = new double[n];
		int[] Xv = new int[n];
		double initValueK = BigDecimal.valueOf(N)
				.divide(BigDecimal.valueOf(n), 5, BigDecimal.ROUND_HALF_EVEN)
				.doubleValue();
		System.out.println("init value for k : " + initValueK);
		for (int j = 0; j < n; j++) {
			Kv[j] = initValueK;
			Xv[j] = 1;
		}

		iter = 1;
		try {
			// construct master solver
			IloCplex master = new IloCplex();
			master.setOut(new NullOutputStream());

			double[] theta_value = new double[simulatedCount];

			IloLinearNumExpr obj = master.linearNumExpr();
			IloNumVar[] K = master.numVarArray(n, 0.0, Double.MAX_VALUE);
			IloIntVar[] X = master.boolVarArray(n);
			IloNumVar[] theta = master.numVarArray(simulatedCount, -1000,
					Double.MAX_VALUE);
			obj.addTerms(flexibleCosts, K);
			for (int j = 0; j < n; j++) {
				obj.addTerm(fixedCost, X[j]);
			}
			for (int j = 0; j < simulatedCount; j++) {
				obj.addTerm(probibility.doubleValue(), theta[j]);
				theta_value[j] = -100;
			}
			master.addMinimize(obj);

			// all demands produced by one plant
			for (int j = 0; j < n; j++)
				master.addGe(master.prod(2 * N, X[j]), K[j]);

			double UB = Double.MAX_VALUE;
			double LB = Double.NEGATIVE_INFINITY;
			double gap = Double.MAX_VALUE;

			int[] currentOptSolution = Lang.clone(Xv);
			double currentOptValue = 0.0;
			int delta = n / 4;

			long start = System.currentTimeMillis();
			while (true) {
				double masterObjV = Double.NEGATIVE_INFINITY;
				double phi = 0.0;

				if (iter > 1) {
					// @enhance Dynamically adjust tolerance
					if (gap > 0.05)
						master.setParam(IloCplex.DoubleParam.EpGap, 0.1);
					else if (gap > 0.03)
						master.setParam(IloCplex.DoubleParam.EpGap, 0.09);
					else if (gap > 0.02)
						master.setParam(IloCplex.DoubleParam.EpGap, 0.08);
					else if (gap > 0.01)
						master.setParam(IloCplex.DoubleParam.EpGap, 0.01);
					else if (gap > 0.008)
						master.setParam(IloCplex.DoubleParam.EpGap, 0.008);
					else if (gap > 0.007)
						master.setParam(IloCplex.DoubleParam.EpGap, 0.007);
					else if (gap > 0.005)
						master.setParam(IloCplex.DoubleParam.EpGap, 0.005);
					else if (gap > 0.003)
						master.setParam(IloCplex.DoubleParam.EpGap, 0.003);
					else if (gap > 0.002)
						master.setParam(IloCplex.DoubleParam.EpGap, 0.002);
					else
						master.setParam(IloCplex.DoubleParam.EpGap, 0.001);
					// end enhance

					// @enhance: trust region
					IloRange trustRegion = null;
					int oneC = 0;
					IloLinearNumExpr trExp = master.linearNumExpr();
					for (int j = 0; j < n; j++) {
						trExp.addTerm(1, X[j]);
						if (currentOptSolution[j] == 1) {
							oneC++;
						}
					}
					trustRegion = master.addLe(trExp, delta + oneC);
					// end enhance

					if (master.solve()) {
						Kv = master.getValues(K);
						for (int j = 0; j < n; j++) {
							Xv[j] = (int) master.getValue(X[j]);
						}
						theta_value = master.getValues(theta);
						masterObjV = master.getObjValue();

						if (masterObjV > LB)
							LB = masterObjV;

						BigDecimal buildCostB = BigDecimal.ZERO;
						for (int j = 0; j < n; j++) {
							if (Xv[j] == 1) {
								buildCostB = buildCostB.add(BigDecimal
										.valueOf(fixedCost));
								buildCostB = buildCostB.add(BigDecimal.valueOf(
										flexibleCosts[j]).multiply(
										BigDecimal.valueOf(Kv[j])));
							}
						}
						phi = buildCostB.doubleValue();

						System.out.println("decision variables for iter("
								+ iter + ") = ");
					} else {
						System.out.println("unsolve status = "
								+ master.getStatus());
					}

					if (trustRegion != null)
						master.delete(trustRegion);

					Lang.print(Xv);
					Lang.print(Kv);
				}
				// solve each dual problem
				for (int m = 0; m < simulatedCount; m++) {
					IloCplex dual = new IloCplex();
					dual.setOut(new NullOutputStream());

					IloNumVar[] pi = dual.numVarArray(N,
							Double.NEGATIVE_INFINITY, 0.0);
					IloNumVar[] sigma = dual.numVarArray(n,
							Double.NEGATIVE_INFINITY, 0.0);

					IloLinearNumExpr sobj = dual.linearNumExpr();
					sobj.add(dual.scalProd(demands[m], pi));
					sobj.add(dual.scalProd(Kv, sigma));
					dual.addMaximize(sobj);

					for (int i = 0; i < N; i++) {
						for (int j = 0; j < n; j++) {
							if (plants.get(j).isAvailable(i))
								dual.addLe(dual.sum(pi[i], sigma[j]),
										-this.p[i]);
						}
					}

					if (dual.solve()) {
						double[] pi_value = dual.getValues(pi);
						double[] sigma_value = dual.getValues(sigma);
						double[] Et = new double[n];
						double set = 0.0;
						for (int i = 0; i < N; i++)
							set += demands[m][i] * pi_value[i];
						double dualObj = dual.getObjValue();

						phi += BigDecimal.valueOf(dualObj)
								.multiply(probibility).doubleValue();

						double Etkv = 0.0;
						for (int j = 0; j < n; j++) {
							Et[j] = -sigma_value[j];
							if (Xv[j] == 1)
								Etkv += Et[j] * Kv[j];
						}
						double wv = set - Etkv;
						if (wv > theta_value[m])
							master.addGe(master.sum(theta[m],
									master.scalProd(Et, K)), set);
					}
					dual.end();
				}
				if (iter > 1 && phi < UB)
					UB = phi;
				// @enhance
				if (phi < currentOptValue) {
					currentOptValue = phi;
					currentOptSolution = Lang.clone(Xv);
				}

				if (iter == 1) {
					gap = UB - LB;
				} else {
					gap = BigDecimal
							.valueOf(UB - LB)
							.divide(BigDecimal.valueOf(-LB), 5,
									BigDecimal.ROUND_HALF_EVEN).doubleValue();
				}
				System.out.println("@" + iter + ": UB = " + UB + "; LB = " + LB
						+ "; gap=" + gap);
				long end = System.currentTimeMillis();
				long time = end - start;
				System.out.println("time = " + time);

				if (gap < this.epsilon || time > 3600000) {
					masterObjV += BigDecimal.valueOf(n)
							.multiply(BigDecimal.valueOf(fixedCost))
							.doubleValue();
					Double[] fobj = { masterObjV };
					Double[] fcap = new Double[n];
					fcap = Lang.convertdouble2Double(Kv);
					finalResult.add(fobj);
					finalResult.add(fcap);

					break;
				}
				iter++;
			}
			master.end();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return finalResult;
	}

	/**
	 * Calculate the flexible cost for a plant based on the number of products
	 * it can produce. c1[1+(countj-1)*delta]
	 * 
	 * @param number
	 *            : the number of products the plant can produce
	 * **/
	private double getFlexibleCost(int number) {
		// linear cost in number
		return BigDecimal
				.valueOf(c1)
				.multiply(
						BigDecimal.valueOf(1).add(
								BigDecimal.valueOf(delta).multiply(
										BigDecimal.valueOf(number - 1))))
				.doubleValue();

		// // convex in number
		// return BigDecimal
		// .valueOf(c1)
		// .multiply(
		// BigDecimal.ONE.add(BigDecimal.valueOf(
		// Math.pow(number - 1, 1.5)).multiply(
		// BigDecimal.valueOf(delta)))).doubleValue();
	}
}
